﻿using System;
using System.Linq;
using System.Configuration;
using System.Collections.Generic;
using NLite.Collections;
using NLite.Log;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using NLite.Internal;

namespace NLite
{
    /// <summary>
    /// Represents errors that occur in the framework.
    /// </summary>
    [Serializable]
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [ComDefaultInterface(typeof(_Exception))]
    public class NLiteException:Exception 
    {
        /// <summary>
        /// 
        /// </summary>
        public ErrorState ErrorState { get; private set; }

         #region Ctor
        /// <summary>
        /// Initializes a new instance of the <c>NLiteException</c> class.
        /// </summary>
        public NLiteException() : base() { ErrorState = new ErrorState(); }
        /// <summary>
        /// Initializes a new instance of the <c>NLiteException</c> class with the specified
        /// error message.
        /// </summary>
        /// <param name="message">The message that describes the error.</param>
        public NLiteException(string message) : base(message) { ErrorState = new ErrorState(); }
        /// <summary>
        /// Initializes a new instance of the <c>NLiteException</c> class with the specified
        /// error message and the inner exception that is the cause of this exception.
        /// </summary>
        /// <param name="message">The message that describes the error.</param>
        /// <param name="innerException">The inner exception that is the cause of this exception.</param>
        public NLiteException(string message, Exception innerException)
            : base(message, innerException)
        {
            var nliteException = innerException as NLiteException;
            ErrorState = nliteException == null ? new ErrorState() : nliteException.ErrorState;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="info"></param>
        /// <param name="context"></param>
        public NLiteException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
        }
        #endregion


        /// <summary>
        /// Gets or sets exception handled
        /// </summary>
        public bool ExceptionHandled
        {
            get;
            set;
        }


    }
    /// <summary>
    /// 
    /// </summary>
    public static class ExceptionService
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ex"></param>
        public static Exception Handle(this Exception ex)
        {
            return ExceptionManager.HandleException(ex);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TNewExcption"></typeparam>
        /// <param name="ex"></param>
        /// <param name="message"></param>
        public static TNewExcption Handle<TNewExcption>(this Exception ex, string message) where TNewExcption : Exception, new()
        {
            return ExceptionManager.HandleAndWrapper<TNewExcption>(ex, message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TNewExcption"></typeparam>
        /// <param name="ex"></param>
        public static TNewExcption Handle<TNewExcption>(this Exception ex) where TNewExcption : Exception, new()
        {
            return ExceptionManager.HandleAndWrapper<TNewExcption>(ex, ex.Message);
        }
    }

    /// <summary>
    /// Represents that the implemented classes are exception handlers.
    /// </summary>
    public interface IExceptionHandler
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        bool HandleException(Exception ex);
    }

    /// <summary>
    /// 
    /// </summary>
    /// <typeparam name="TException"></typeparam>
    public abstract class ExceptionHandler<TException> : IExceptionHandler where TException :Exception
    {
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        protected abstract bool DoHandle(TException ex);

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public virtual bool HandleException(Exception ex)
        {
            return DoHandle(ex as TException);
        }
    }


   
    

    /// <summary>
    /// 
    /// </summary>
    public sealed class ExceptionManager
    {
        private static ILog log = LogManager.GetLogger(typeof(ExceptionManager));

        private static readonly Map<Type, ExceptionHandlerInfo> HandlerStore = new Map<Type, ExceptionHandlerInfo>();
        private static readonly Map<Type, HashSet<IExceptionHandler>> handlersChain = new Map<Type, HashSet<IExceptionHandler>>();

        private ExceptionManager()
        {
        }


        private static IEnumerable<IExceptionHandler> GetBaseHandlers(Type thisType)
        {
            Type baseType = thisType.BaseType;
            while (baseType != typeof(object))
            {
                if (HandlerStore.ContainsKey(baseType))
                {
                    var item = HandlerStore[baseType];
                    foreach (var handler in item.Handlers)
                        yield return handler;
                    break;
                }
                baseType = baseType.BaseType;
            }
           
        }

        private static void AppendHandlerChain(Type type, ExceptionHandlerInfo info)
        {
            var sourceHandlers = info.Handlers;


            var targetHandlers = handlersChain.GetOrAdd(type, () => new HashSet<IExceptionHandler>());
            sourceHandlers.ForEach(p => targetHandlers.Add(p));

            if (!info.DirectHandle)
            {
                var handlersFromBase = GetBaseHandlers(type);
                handlersFromBase.ForEach(p => targetHandlers.Add(p));
            }
            handlersChain.Add(type, targetHandlers);
        }

        private static bool HandleExceptionInternal(Exception ex)
        {
            if (ex == null)
                ex = new ArgumentNullException("ex");

            var nliteException = ex as NLiteException;
            if (nliteException != null)
            {
                if (nliteException.ExceptionHandled)
                    return true;
                else
                    nliteException.ExceptionHandled = true;
            }

            log.Error(ex.Message, ex);

            Type exType = ex.GetType();
            if (!handlersChain.ContainsKey(exType))
                return false;

            var handlers = handlersChain[exType];
            if (handlers == null || handlers.Count == 0)
                return false;

            bool ret = false;
            handlers.ForEach(p =>ret |= p.HandleException(ex));
            return ret;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="exceptionType"></param>
        /// <param name="handler"></param>
        /// <param name="directHandle"></param>
        public static void RegisterHandler(Type exceptionType, IExceptionHandler handler,bool directHandle )
        {
            var item = HandlerStore.GetOrAdd(exceptionType, () => new ExceptionHandlerInfo { DirectHandle = directHandle });
            item.Handlers.Add(handler);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <typeparam name="TExceptionHandler"></typeparam>
        /// <param name="directHandle"></param>
        public static void RegisterHandler<TException, TExceptionHandler>(bool directHandle )
            where TException : Exception
            where TExceptionHandler : IExceptionHandler, new()
        {
            RegisterHandler(typeof(TException), new TExceptionHandler(), directHandle);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="directHandle"></param>
        /// <param name="handler"></param>
        public static void RegisterHandler<TException>(bool directHandle,Func<TException,bool> handler) where TException : Exception
        {
            var h = new DelegateExeptionHandler<TException>(handler);
            RegisterHandler(typeof(TException), h, directHandle);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="handler"></param>
        public static void RegisterHandler<TException>(Func<TException, bool> handler) where TException : Exception
        {
            var h = new DelegateExeptionHandler<TException>(handler);
            RegisterHandler(typeof(TException), h, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="directHandle"></param>
        /// <param name="handler"></param>
        public static void RegisterHandler<TException>(bool directHandle, Action<TException> handler) where TException : Exception
        {
            var h = new DelegateExeptionHandler<TException>(handler);
            RegisterHandler(typeof(TException), h, directHandle);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="handler"></param>
        public static void RegisterHandler<TException>( Action<TException> handler) where TException : Exception
        {
            var h = new DelegateExeptionHandler<TException>(handler);
            RegisterHandler(typeof(TException), h, true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <typeparam name="TExceptionHandler"></typeparam>
        public static void RegisterHandler<TException, TExceptionHandler>()
            where TException : Exception
            where TExceptionHandler : IExceptionHandler, new()
        {
            RegisterHandler(typeof(TException), new TExceptionHandler(), true);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static Exception HandleException(Exception ex)
        {
            ExceptionManager.HandleExceptionInternal(ex);
            return ex;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="ex"></param>
        /// <returns></returns>
        public static TException HandleException<TException>(TException ex)
            where TException : Exception
        {
            ExceptionManager.HandleExceptionInternal((Exception)ex);
            return ex;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="message"></param>
        /// <returns></returns>
        public static TException HandleAndWrapper<TException>(string message) where TException : Exception, new()
        {
            return HandleAndWrapper<TException>(null, message);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="TException"></typeparam>
        /// <param name="ex"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public static TException HandleAndWrapper<TException>(Exception ex, string message)
            where TException : Exception, new()
        {
            var targetException = default(TException);

            if (ex != null)
            {
                var ctor = typeof(TException).GetConstructor(new Type[] { typeof(string), typeof(Exception) });
                if (ctor != null)
                    targetException = ctor.Invoke(new object[] { message, ex }) as TException;
            }
            else if (!string.IsNullOrEmpty(message))
            {
                var ctor = typeof(TException).GetConstructor(new Type[] { typeof(string) });
                if (ctor != null)
                    targetException = ctor.Invoke(new object[] { message }) as TException;
            }
            else
                targetException = new TException();

            targetException.Handle();
            return targetException;
        }

       

      
       
    }

    namespace Internal
    {
        class DelegateExeptionHandler<TException> : ExceptionHandler<TException> where TException : Exception
        {
            Func<TException, bool> Handler;
            public DelegateExeptionHandler(Func<TException, bool> handler)
            {
                Handler = handler;
            }

            public DelegateExeptionHandler(Action<TException> handler)
                : this(ex => { handler(ex); return true; })
            {
            }

            protected override bool DoHandle(TException ex)
            {
                if (Handler != null)
                    return Handler(ex);
                return true;
            }
        }

        class ExceptionHandlerInfo
        {
            public bool DirectHandle;
            public HashSet<IExceptionHandler> Handlers;

            public ExceptionHandlerInfo()
            {
                DirectHandle = true;
                Handlers = new HashSet<IExceptionHandler>();
            }
        }
    }
}